package ar.edu.unlu.sistemas.p2p.business.module;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import android.util.Log;
import ar.edu.unlu.sistemas.p2p.android.sql.IndexOpenHelper;
import ar.edu.unlu.sistemas.p2p.business.file.FileController;
import ar.edu.unlu.sistemas.p2p.business.index.IndexController;
import ar.edu.unlu.sistemas.p2p.business.msg.MessageFactory;
import ar.edu.unlu.sistemas.p2p.business.msg.MessageInterpreter;
import ar.edu.unlu.sistemas.p2p.business.msg.PeerConnectionACKMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PeerConnectionMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PeerDisconnectionACKMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PeerDisconnectionMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PingMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PingResult;
import ar.edu.unlu.sistemas.p2p.business.msg.PongMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.QueryMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.QueryResponseMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.QueryResult;
import ar.edu.unlu.sistemas.p2p.business.net.ProtocolListenerController;
import ar.edu.unlu.sistemas.p2p.business.net.tcp.PeerTCPConnection;
import ar.edu.unlu.sistemas.p2p.business.net.tcp.TCPListener;
import ar.edu.unlu.sistemas.p2p.business.peer.Peer;
import ar.edu.unlu.sistemas.p2p.business.peer.PeerController;
import ar.edu.unlu.sistemas.p2p.business.strategy.ProcessQueryStrategy;
import ar.edu.unlu.sistemas.p2p.business.strategy.impl.ProcessQueryBooleanModeStrategyImpl;
import ar.edu.unlu.sistemas.p2p.business.strategy.impl.ProcessQueryPerformedStrategyImpl;
import ar.edu.unlu.sistemas.p2p.business.strategy.impl.ProcessQueryStandardStrategyImpl;
import ar.edu.unlu.sistemas.p2p.business.task.Result;
import ar.edu.unlu.sistemas.p2p.business.task.Task;
import ar.edu.unlu.sistemas.p2p.business.task.TaskController;
import ar.edu.unlu.sistemas.p2p.business.util.ByteHelper;
import ar.edu.unlu.sistemas.p2p.business.util.Logger;
import ar.edu.unlu.sistemas.p2p.business.util.Params;
import ar.edu.unlu.sistemas.p2p.business.util.StringUtils;
import ar.edu.unlu.sistemas.p2p.business.vo.DocumentVO;
import ar.edu.unlu.sistemas.p2p.business.vo.PeerVO;
import ar.edu.unlu.sistemas.p2p.business.vo.PingResultVO;
import ar.edu.unlu.sistemas.p2p.business.vo.QueryResultVO;
import ar.edu.unlu.sistemas.p2p.business.vo.TermFrecuencyVO;
import ar.edu.unlu.sistemas.p2p.business.vo.UpdateIndexVO;
import ar.edu.unlu.sistemas.p2p.business.vo.WordVO;

public class AppModule {
	
	private static AppModule INSTANCE;
	private ProtocolListenerController listenerController;
	private PeerController peerController;
	private TaskController taskController;
	private IndexController indexController;
	private FileController fileController;
	public boolean indexUpdateLaunched = false;
	
	private FileController getFileController() {
		return fileController;
	}

	private void setFileController(FileController fileController) {
		this.fileController = fileController;
	}

	private TaskController getTaskController() {
		return taskController;
	}

	private void setTaskController(TaskController taskController) {
		this.taskController = taskController;
	}

	private PeerController getPeerController() {
		return peerController;
	}

	private void setPeerController(PeerController peerController) {
		this.peerController = peerController;
	}

	private ProtocolListenerController getListenerController() {
		return listenerController;
	}

	private void setListenerController(ProtocolListenerController listenerController) {
		this.listenerController = listenerController;
	}
	
	private IndexController getIndexController() {
		return indexController;
	}

	private void setIndexController(IndexController indexController) {
		this.indexController = indexController;
	}
	
	public static AppModule getInstance() {
		return INSTANCE;
	}
	
	/**
	 * Método que define el patrón singleton, permitendo pasarle por parámetro el puerto de escucha
	 * 
	 * @param port
	 * @return AppModule instance
	 */
	public static AppModule getInstanceWithParams(int port, IndexOpenHelper index, String macAddr,String alias) {
		if (INSTANCE == null) {
			INSTANCE = new AppModule(port, index, macAddr, alias);
		}
		return INSTANCE;
	}
	

	/**
	 * Constructor con puerto de escucha como parámetro, inicializa todos los controllers
	 */
	private AppModule(int port, IndexOpenHelper index, String macAddress, String alias) {
		Params.TCP_LISTEN_PORT = port;
		Params.PEER_ALIAS = alias;
		this.setListenerController(ProtocolListenerController.getInstance());
		this.setPeerController(PeerController.getInstance());
		this.setTaskController(TaskController.getInstance());
		this.setIndexController(IndexController.getInstance());
		this.getIndexController().setIndex(index);
		this.setFileController(FileController.geInstance());
		this.getFileController().setIndex(this.getIndexController());
		this.getListenerController().addListener(ProtocolListenerController.UDP);
		this.connect(macAddress);
	}
	
	public boolean isConnected() {
		return (this.getListenerController().getListener(ProtocolListenerController.TCP) != null);
	}
	
	public void connect(String macAddress) {
		TCPListener tcp = null;
		if (macAddress != null) {
			tcp = (TCPListener) this.getListenerController().addListener(ProtocolListenerController.TCP);
		}
		if (tcp != null) {
			this.peerController.setLocalPeer(new Peer(tcp.getSocket().getLocalSocketAddress(), macAddress));
		} else {
			this.peerController.setLocalPeer(new Peer());
		}
		this.getPeerController().getLocalPeer().setName(Params.PEER_ALIAS);
	}
	
	/**
	 * Define el nombre del peer que representa la aplicación. Con este nombre lo verán los otros peers.
	 * Por ahora se realiza sólo en etapa de inicialización. Para permitir modificación es necesario 
	 * definir un nuevo mensaje de protocolo para notificar a los vecinos de la misma
	 * 
	 * @param name
	 */
	public void setPeerName(String name) {
		this.getPeerController().getLocalPeer().setName(name);
	}
	
	/**
	 * Realiza una solicitud de conexión a la ip:puerto pasada como parámetro.
	 * Se espera que esa dirección corresponda a otro peer que dialogue el mismo protocolo.
	 * 
	 * @param dstAddress
	 * @param dstPort
	 */
	public void requestConnectionToPeer(InetAddress dstAddress, int dstPort) {
		Logger.logInfo(this.getClass().getName(), "Solicita conexion a "+dstAddress.getHostAddress()+":"+String.valueOf(dstPort));
		try {
			PeerTCPConnection conn = new PeerTCPConnection(new Socket(dstAddress,dstPort));
			PeerConnectionMessage msg = (PeerConnectionMessage) MessageFactory.getMessage(MessageFactory.CODE_CONNECT_PEER_NODE);
			Peer thisPeer = this.getPeerController().getLocalPeer();
			msg.setPeerName(thisPeer.getName());
			msg.setPort(thisPeer.getAddress().getPort());
			msg.setPeerId(thisPeer.getId());
			conn.send(msg.toXMLString());
		} catch (IOException ioe) {
			Logger.logError(this.getClass().getName(),"No se pudo conectar a "+dstAddress.getHostAddress()+":"+String.valueOf(dstPort), ioe);
		}
		
	}
	
	/**
	 * Arriva una solicitud de conexión por parte de otro peer. Este es agregado a la lista de vecinos
	 * y se envía una respuesta para finalizar el handshake.
	 * 
	 * @param connection
	 * @param msg
	 */
	public void connectionRequestedByPeer(PeerTCPConnection connection, PeerConnectionMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Conexion solicitada por "+msg.getPeerName());
		Peer peer = new Peer(msg.getPeerName(),msg.getPort(),connection,msg.getPeerId());
		connection.setPeer(peer);
		this.getPeerController().addPeer(peer);
		peer.sendAcceptConnection();
	}
	
	/**
	 * El peer al cual le envíamos la solicitud respondió la misma, por lo que lo agregamos a la
	 * lista de adyacentes.
	 * 
	 * @param connection
	 * @param msg
	 */
	public void peerConnected(PeerTCPConnection connection, PeerConnectionACKMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Conectado con "+msg.getPeerName());
		Peer peer = new Peer(connection, msg.getPeerName(), msg.getPeerId());
		this.getPeerController().addPeer(peer);
	}
	
	/**
	 * Se realiza una solicitud de desconexión al peer. Se realiza una desconexión de una manera
	 * amigable para que el otro pueda finalizar algunas tareas que involucran a este peer.
	 * La otra opción es la desconexión abrupta, la manejaremos con el uso de excepciones.
	 * 
	 * @param peerId
	 */
	public void requestDisconnectionToPeer(String peerId) {
		Peer peer = this.getPeerController().getPeer(peerId);
		if (peer != null ) {
			Logger.logInfo(this.getClass().getName(), "Solicita desconexion a "+peer.toString());
			peer.sendRequestDisconnection();
		}
	}
	
	/**
	 * Un peer adyacente solicita una desconexión. Se termina todo lo que tiene que hacer con éste,
	 * se lo quita de la lista y se le envía una respuesta.
	 * 
	 * @param peer
	 * @param msg
	 */
	public void disconnectionRequestedByPeer(Peer peer, PeerDisconnectionMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Desconexion solicitada por "+peer.toString());
		this.getPeerController().removePeer(peer.getId());
		peer.sendAcceptDisconnection();
		peer.disconnect();
	}
	
	/**
	 * Un peer adyacente al cual le enviamos una solicitud de desconexión, responde a la misma
	 * por lo que lo quitamos de la lista y nos desconectamos
	 * 
	 * @param peer
	 * @param msg
	 */
	public void peerDisconected(Peer peer, PeerDisconnectionACKMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Desconectado de "+peer.toString());
		this.getPeerController().removePeer(peer.getId());
		peer.disconnect();
	}
	
	/**
	 * Simplemente remueve el peer. Se utiliza en el caso de que se cierre inesperadamente una conexión
	 * o expire el time out.
	 * 
	 * @param peerId
	 */
	public void removePeer(String peerId) {
		this.getPeerController().removePeer(peerId);
	}
	
	/**
	 * Retorna todo los peers representados en value-objects
	 */
	public Collection<PeerVO> getAllPeers() {
		return this.getPeerController().getAllPeers();
	}
	
	/**
	 * Dispara el evento de la realización de un ping. Recorre todos los peers vecinos y les envía
	 * el mensaje correspondiente.
	 * 
	 * @return el id del mensaje ping
	 */
	public String doPing() {
		Logger.logInfo(this.getClass().getName(), "Realizo un ping");
		Iterator<PeerVO> peers = this.getPeerController().getAllPeers().iterator();
		PeerVO peerVO;
		String msgId = this.generateMsgId();
		Task task = new Task(msgId);
		task.setMsgType(MessageFactory.CODE_PING);
		this.getTaskController().addTask(task);
		//Agrego el resultado del peer local
		task.addPeer(this.getPeerController().getLocalPeer().getId());
		Collection<Result> results = new ArrayList<Result>();
		results.add(new PingResult(
				this.getPeerController().getLocalPeer().getAddress(),
				this.getIndexController().findAllDocs().size()).getVO());
		task.addPeerResults(this.getPeerController().getLocalPeer().getId(),results);
		//Agrego los peers a los que voy a propagar el mensaje
		while (peers.hasNext()) {
			peerVO = peers.next();
			task.addPeer(peerVO.getId());
		}
		task.start();
		peers = this.getPeerController().getAllPeers().iterator();
		while (peers.hasNext()) {
			peerVO = peers.next();
			Logger.logInfo(this.getClass().getName(), "Envio ping a "+peerVO.toString());
			this.getPeerController().getPeer(peerVO.getId()).sendPing(msgId);
		}
		return msgId;
	}
	
	/**
	 * Se dispara cuando se detecta la recepción de un mensaje del tipo ping por parte de un
	 * peer vecino, lo propaga a  sus otros vecinos y inicia una tarea que se encarga de 
	 * controlar la recolección de resultados de la propagación. Es probable que anteriormente 
	 * ya se haya recibido el mismo mensaje por parte de otro peer, por lo que se lo descarta 
	 * si ya se respondio por ese mensaje o se devuelve vacio si aun esta ese ping en proceso, 
	 * para que el peer en cuestion no aparezca más de una vez en el resultado total del ping
	 * 
	 * @param peer
	 * @param msg
	 */
	public void receivePingFromPeer(Peer peer, PingMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Recibo ping de "+peer.toString());
		//El mensaje está en el historico? Ya se envió una respuesta a este ping?
		if (!this.getTaskController().isTaskInHistory(msg.getHeader().getMsgId())) {
			Logger.logDebug(this.getClass().getName(), "El ping "+msg.getHeader().getMsgId()+" no está en el historico");
			//El mensaje figura ya en una tarea actual? Está aún en la espera de resultados de otros?
			if (!this.getTaskController().containsTask(msg.getHeader().getMsgId())) {
				Logger.logDebug(this.getClass().getName(), "Ping "+msg.getHeader().getMsgId()+" aun no fue procesado");
				if (msg.getHeader().getTtl() == 0) {
					//Si tiene TTL = 0 no se puede propagar por lo que no tiene sentido iniciar una task
					Logger.logDebug(this.getClass().getName(), "Tiene TTL = 0");
					PongMessage pongMsg = (PongMessage) MessageFactory.getMessage(MessageFactory.CODE_PONG);
					pongMsg.getHeader().setMsgId(msg.getHeader().getMsgId());
					pongMsg.addResult(new PingResult(
							this.getPeerController().getLocalPeer().getAddress(),
							this.getIndexController().findAllDocs().size()));
					peer.sendPong(pongMsg);
					this.getTaskController().addTaskToHistory(msg.getHeader().getMsgId());
				} else {
					/*El ping debe ser propagado y se debe iniciar una task que aguarde por la respuesta
					de la propagación*/
					Logger.logDebug(this.getClass().getName(), "El TTL > 0");
					Iterator<PeerVO> peers = this.getPeerController().getAllPeers().iterator();
					PeerVO peerVO;
					Task task = new Task(msg.getHeader().getMsgId(),peer.getId(),msg.getHeader().getType());
					this.getTaskController().addTask(task);
					task.start();
					while (peers.hasNext()) {
						peerVO = peers.next();
						if (!peerVO.getId().equals(peer.getId())) {
							task.addPeer(peerVO.getId());
						}
					}
					peers = this.getPeerController().getAllPeers().iterator();
					while (peers.hasNext()) {
						peerVO = peers.next();
						if (!peerVO.getId().equals(peer.getId())) {
							Logger.logInfo(this.getClass().getName(), "Reenvio ping a "+peerVO.toString());
							this.getPeerController().getPeer(peerVO.getId()).forward(msg);
						}
					}
				}
			} else {
				/*Como ya hay un task que espera los resultados de la propagación y mergea la info de este 
				con la de los vecino, se envía un mensaje con resultado vacío la para evitar duplicados
				en los resultados*/
				Logger.logDebug(this.getClass().getName(), "Ping "+msg.getHeader().getMsgId()+" en proceso");
				peer.sendEmptyPong(msg.getHeader().getMsgId());
			}
		}
			
	}
	
	/**
	 * Se dispara este metodo cuando se recibe un pong (respuesta de un ping) por parte de un peer.
	 * Simplemente se acumula el resultado retornado desde este con el resto para luego propagar la
	 * respuesta hasta que vuelva a el origen
	 * 
	 * @param peer
	 * @param msg
	 */
	public void receivePongFromPeer(Peer peer, PongMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Recibo pong de "+peer.toString());
		this.getTaskController().addResultsToTask(msg.getHeader().getMsgId(), peer.getId(), msg.getAllResults());
	}
	
	/**
	 * Finalizado el task, por resultados completos o por expiracion de tiempo, se procede a enviar
	 * la respuesta al peer originario del ping.
	 * 
	 * @param msgId
	 */
	public void sendPong(String msgId) {
		Logger.logInfo(this.getClass().getName(), "Termina task y envio el pong");
		Task task = this.getTaskController().removeTask(msgId);
		if (task != null) {
			Iterator<Result>  resultsIt = task.getAllResults().iterator(); 
			PongMessage msg = (PongMessage) MessageFactory.getMessage(MessageFactory.CODE_PONG);
			msg.addResult(new PingResult(
					this.getPeerController().getLocalPeer().getAddress(),
					this.getIndexController().findAllDocs().size()));
			msg.getHeader().setMsgId(task.getMsgId());
			while(resultsIt.hasNext()) {
				PingResultVO vo = (PingResultVO) resultsIt.next();
				msg.addResult(new PingResult(vo.getAddress(), vo.getnFiles()));
			}
			Logger.logInfo(this.getClass().getName(), "Envio pong a "+task.getSourcePeerId());
			this.getPeerController().getPeer(task.getSourcePeerId()).sendPong(msg);
		}
	}
	
	public void stopTCPListener() {
		this.getListenerController().removeListener(ProtocolListenerController.TCP);
	}
	
	public void startTCPListener() {
		this.getListenerController().addListener(ProtocolListenerController.TCP);
	}
	
	public void stopListen() {
		Logger.logInfo(this.getClass().getName(),"Finaliza todas las escuchas del peer");
		this.getListenerController().stopAllListeners();
	}
	
	/**
	 * Genera el id para un mensaje que lo va identificar a lo largo de toda la propagación
	 * 
	 * @return
	 */
	private String generateMsgId() {
		byte[] arr = new byte[4];
		ByteHelper.longNumIntoByteArr(System.currentTimeMillis(), arr, 0);
		return this.getPeerController().getLocalPeer().getId() + ByteHelper.getHex(arr);
	}
	
	/**
	 * Va a buscar y retorna los resultados de un task
	 * 
	 * @param msgId
	 * @return coleccion de Result
	 */
	public Collection<Result> getTaskResults(String msgId) {
		return this.getTaskController().getTaskResults(msgId);
	}
	
	/**
	 * Va a buscar, elimina y retorna los resultados de un task
	 * 
	 * @param msgId
	 * @return coleccion de Result
	 */
	public Collection<Result> finishTask(String msgId) {
		return this.getTaskController().removeTaskAndGetResults(msgId);
	}
	
	/**
	 * Consulta si una tarea pendiente está finalizada
	 * 
	 * @param msgId
	 * @return verdadero si está finlizada
	 */
	public boolean isTaskFinished(String msgId) {
		return this.getTaskController().isTaskFinished(msgId);
	}
	
	/**
	 * Va a buscar al índice los términos que subcontienen la palabra pasado por parametro
	 * 
	 * @param word
	 * @return colección de value-objects word
	 */
	public Collection<WordVO> findWords(String word) {
		if (!StringUtils.esStringVacioONull(word)) {
			Log.i(this.getClass().getSimpleName(), "Voy a buscar el termino: "+word);
			return this.getIndexController().findWordsLikeName(word);
		} else {
			Log.i(this.getClass().getSimpleName(), "Voy a buscar todos los terminos");
			return this.getIndexController().findAllWords();
		}
	}
	
	/**
	 * Va a buscar al índice los documentos cuyo nombre subcontiene el pasado por parametro
	 * 
	 * @param word
	 * @return colección de value-objects document
	 */
	public Collection<DocumentVO> findDocs(String doc) {
		if (!StringUtils.esStringVacioONull(doc)) {
			Log.i(this.getClass().getSimpleName(), "Voy a buscar el documento: "+doc);
			return this.getIndexController().findDocsLikeName(doc);
		} else {
			Log.i(this.getClass().getSimpleName(), "Voy a buscar todos los documentos");
			return this.getIndexController().findAllDocs();
		}
	}
	
	/**
	 * Va a buscar ocurrencias en el índice invertido. Se puede realizar la busqueda por nombre
	 * de documento o por término, o por los dos simultáneamente (caso para el no trae más de
	 * una fila como resultado)
	 * 
	 * @param doc
	 * @param word
	 * @return colección de value-objects de término-frecuencia
	 */
	public Collection<TermFrecuencyVO> findTFs(String doc, String word) {
		if (StringUtils.esStringVacioONull(word) && !StringUtils.esStringVacioONull(doc)) {
			Log.i(this.getClass().getSimpleName(), "Voy a buscar ocurrencias en el índice invertido para el documento: "+doc);
			return this.getIndexController().findTFsByDocument(doc);
		} else if (!StringUtils.esStringVacioONull(word) && StringUtils.esStringVacioONull(doc)) {
			Log.i(this.getClass().getSimpleName(), "Voy a buscar ocurrencias en el índice invertido para el termino: "+word);
			return this.getIndexController().findTFsByWord(word);
		} else if (!StringUtils.esStringVacioONull(word) && !StringUtils.esStringVacioONull(doc)) {
			Log.i(this.getClass().getSimpleName(), "Voy a buscar la frecuencia del termino "+word+" en el documento "+doc);
			TermFrecuencyVO tfVO = this.getIndexController().findTFByWordDocument(word, doc);
			Collection<TermFrecuencyVO> out = new ArrayList<TermFrecuencyVO>();
			if (tfVO != null) {
				out.add(tfVO);
			}
			return out;
		} else {
			Log.i(this.getClass().getSimpleName(),"Como no se paso ningun parametro no busco nada");
			return new ArrayList<TermFrecuencyVO>();
		}
	}
	
	/**
	 * Agrega una palabra al índice.
	 * Este método se utiliza sólo para pruebas
	 * 
	 * @param word
	 */
	public void addWord(String word) {
		this.getIndexController().insertWord(word);
	}
	
	/**
	 * Agrega una documento al índice.
	 * Este método se utiliza sólo para pruebas
	 * 
	 * @param doc
	 */
	public void addDoc(String doc) {
		this.getIndexController().insertDocImpl(doc);
	}
	
	/**
	 * Agrega una ocurrencia al índice invertido.
	 * Este método se utiliza sólo para pruebas
	 * 
	 * @param doc
	 * @param word
	 * @param value
	 */
	public void addTF(String doc, String word, float value) {
		this.getIndexController().insertTf(doc, word, value);
	}
	
	/**
	 * Almacena en la SDCard e indexa un archivo de texto 
	 * 
	 * @param file InputStream del archivo
	 * @param fileName nombre del archivo
	 */
	public void addFile(File file, String fileName) {
		try {
			FileReader fr = new FileReader(file);
			this.getFileController().addFile(fr, fileName);
		} catch (FileNotFoundException fnfe) {
			Logger.logError(MessageInterpreter.class.getSimpleName(), " Error agregando "+file.getName(),fnfe);
		}
	}
	
	/**
	 * Borra del indice un documento correspondido con un archivo te texto que ya no existe 
	 * 
	 * @param docName nombre del documento
	 */
	public void removeDoc(String docName) {
		this.getIndexController().removeDoc(docName);
	}
	
	/**
	 * Retorna el subconjunto de los archivos enviados por parametro que no se encuentran indexados
	 * Elimina aquellos documentos indexados que ya no se encuentran en el directorio
	 * 
	 * @param files
	 * @return subconjunto de nombres de archivos no indexados
	 */
	public Collection<UpdateIndexVO> getNotIndexedFiles(String[] files) {
		List<String> filesList = Arrays.asList(files);
		Log.i(this.getClass().getSimpleName(), "Archivos en el directorio "+filesList.toString());
		List<DocumentVO> docs = (List<DocumentVO>) this.getIndexController().findAllDocs();
		Log.i(this.getClass().getSimpleName(), "Archivos indexados "+docs.toString());
		Collection<String> docNames = new ArrayList<String>();
		Iterator<DocumentVO> docsIt = docs.iterator();
		DocumentVO tempDocVO = new DocumentVO();
		Collection<UpdateIndexVO> outFiles = new ArrayList<UpdateIndexVO>();
		//Primero obtengo aquellos documentos que ya no están en el directorio para borrar del indice
		while (docsIt.hasNext()) {
			tempDocVO = docsIt.next();
			docNames.add(tempDocVO.getName());
			if (!filesList.contains(tempDocVO.getName())) {
				outFiles.add(UpdateIndexVO.removeDoc(tempDocVO.getName()));
			}
		}
		Iterator<String> fileListIt = filesList.iterator();
		//Luego identifico aquellos archivos del directorio que no están como documentos
		while (fileListIt.hasNext()) {
			tempDocVO.setName(fileListIt.next());
			if (!docNames.contains(tempDocVO.getName())) outFiles.add(UpdateIndexVO.insertDoc(tempDocVO.getName()));
		}
		Log.i(this.getClass().getSimpleName(), "Acciones para actualizar "+outFiles.toString());
		return outFiles;
	}
	
	public Collection<DocumentVO> findAllDocs(){
		return this.findDocs(null);
	}
	
	private HashMap<String,TermFrecuencyVO> findTfsByDoc(DocumentVO docVO) {
		Iterator<TermFrecuencyVO> allTerms = this.findTFs(docVO.getName(), null).iterator();
		HashMap<String,TermFrecuencyVO> termsMap = new HashMap<String,TermFrecuencyVO>();
		TermFrecuencyVO termVO;
		while (allTerms.hasNext()) {
			termVO = allTerms.next();
			termsMap.put(termVO.getWord().getName(), termVO);
		}
		return termsMap;
	}
	
	public HashMap<String,TermFrecuencyVO> findTfsByDocAndWords(DocumentVO docVO, String[] words) {
		Iterator<TermFrecuencyVO> allTerms = this.getIndexController().findTFsByDocumentAndWords(docVO.getName(),words).iterator();
		HashMap<String,TermFrecuencyVO> termsMap = new HashMap<String,TermFrecuencyVO>();
		TermFrecuencyVO termVO;
		while (allTerms.hasNext()) {
			termVO = allTerms.next();
			termsMap.put(termVO.getWord().getName(), termVO);
		}
		return termsMap;
	}
	
	public Collection<QueryResultVO> processQuery(String query) {
		Logger.logInfo(this.getClass().getSimpleName(),"Realizo consulta: "+query);
		ProcessQueryStrategy strategy;
		if (Params.BOOLEAN_EXTENDED_MODE) {
			strategy = new ProcessQueryBooleanModeStrategyImpl();
		}else if (Params.PROCESS_QUERY_PERFORMED) {
			strategy = new ProcessQueryPerformedStrategyImpl();
		} else {
			strategy = new ProcessQueryStandardStrategyImpl();
		}
		return strategy.execute(query);
	}
	
	/**
	 * Dispara el evento de la realización de una query. La lógica es análoga al la del ping.
	 * 
	 * @param la query representada por un string
	 * @return el id del mensaje ping
	 */
	public String doQuery(String query) {
		Logger.logInfo(this.getClass().getName(), "Realizo una query");
		Iterator<PeerVO> peers = this.getPeerController().getAllPeers().iterator();
		PeerVO peerVO;
		String msgId = this.generateMsgId();
		Task task = new Task(msgId);
		task.setMsgType(MessageFactory.CODE_QUERY);
		this.getTaskController().addTask(task);
		while (peers.hasNext()) {
			peerVO = peers.next();
			task.addPeer(peerVO.getId());
		}
		task.addPeer(this.getPeerController().getLocalPeer().getId());
		task.start();
		peers = this.getPeerController().getAllPeers().iterator();
		while (peers.hasNext()) {
			peerVO = peers.next();
			Logger.logInfo(this.getClass().getName(), "Envio query a"+peerVO.toString());
			this.getPeerController().getPeer(peerVO.getId()).sendQuery(msgId,query);
		}
		Iterator<QueryResultVO> results = this.processQuery(query).iterator();
		QueryResponseMessage queryResponseMsg = (QueryResponseMessage) MessageFactory.getMessage(MessageFactory.CODE_QUERY_RESP);
		QueryResultVO resultVO;
		while (results.hasNext()) {
			resultVO = results.next();
			queryResponseMsg.addResult(new QueryResult(resultVO));
		}
		this.getTaskController().addResultsToTask(msgId,this.getPeerController().getLocalPeer().getId(), queryResponseMsg.getAllResults());
		return msgId;
	}
	
	/**
	 * Se dispara cuando se detecta la recepción de un mensaje del tipo query por parte de un
	 * peer vecino, lo propaga a  sus otros vecinos y inicia una tarea que se encarga de 
	 * controlar la recolección de resultados de la propagación. Es probable que anteriormente 
	 * ya se haya recibido el mismo mensaje por parte de otro peer, por lo que se lo descarta 
	 * si ya se respondio por ese mensaje o se devuelve vacio si aun esta esa query en proceso, 
	 * para que los resultados por parte del peer en cuestión no se repitan en el resultado global
	 * 
	 * @param peer
	 * @param msg
	 */
	public void receiveQueryFromPeer(Peer peer, QueryMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Recibo query de "+peer.toString());
		//El mensaje está en el historico? Ya se envió una respuesta a esta query?
		if (!this.getTaskController().isTaskInHistory(msg.getHeader().getMsgId())) {
			Logger.logDebug(this.getClass().getName(), "La query "+msg.getHeader().getMsgId()+" no está en el historico");
			//El mensaje figura ya en una tarea actual? Está aún en la espera de resultados de otros?
			if (!this.getTaskController().containsTask(msg.getHeader().getMsgId())) {
				Logger.logDebug(this.getClass().getName(), "Query "+msg.getHeader().getMsgId()+" aun no fue procesado");
				if (msg.getHeader().getTtl() == 0) {
					//Si tiene TTL = 0 no se puede propagar por lo que no tiene sentido iniciar un task
					Logger.logDebug(this.getClass().getName(), "Tiene TTL = 0");
					QueryResponseMessage queryResponseMsg = (QueryResponseMessage) MessageFactory.getMessage(MessageFactory.CODE_QUERY_RESP);
					queryResponseMsg.getHeader().setMsgId(msg.getHeader().getMsgId());
					Iterator<QueryResultVO> results = this.processQuery(msg.getQuery()).iterator();
					QueryResultVO resultVO;
					while (results.hasNext()) {
						resultVO = results.next();
						queryResponseMsg.addResult(new QueryResult(resultVO));
					}
					peer.sendQueryResponse(queryResponseMsg);
					this.getTaskController().addTaskToHistory(msg.getHeader().getMsgId());
				} else {
					/*La query debe ser propagada y se debe iniciar una task que aguarde por la respuesta
					de la propagación*/
					Logger.logDebug(this.getClass().getName(), "El TTL > 0");
					Iterator<PeerVO> peers = this.getPeerController().getAllPeers().iterator();
					PeerVO peerVO;
					Task task = new Task(msg.getHeader().getMsgId(),peer.getId(),msg.getHeader().getType());
					this.getTaskController().addTask(task);
					task.start();
					while (peers.hasNext()) {
						peerVO = peers.next();
						if (!peerVO.getId().equals(peer.getId())) {
							task.addPeer(peerVO.getId());
						}
					}
					task.addPeer(this.getPeerController().getLocalPeer().getId());
					peers = this.getPeerController().getAllPeers().iterator();
					while (peers.hasNext()) {
						peerVO = peers.next();
						if (!peerVO.getId().equals(peer.getId())) {
							Logger.logInfo(this.getClass().getName(), "Reenvio query  a "+peerVO.toString());
							this.getPeerController().getPeer(peerVO.getId()).forward(msg);
						}
					}
					Iterator<QueryResultVO> results = this.processQuery(msg.getQuery()).iterator();
					QueryResponseMessage queryResponseMsg = (QueryResponseMessage) MessageFactory.getMessage(MessageFactory.CODE_QUERY_RESP);
					QueryResultVO resultVO;
					while (results.hasNext()) {
						resultVO = results.next();
						queryResponseMsg.addResult(new QueryResult(resultVO));
					}
					this.getTaskController().addResultsToTask(msg.getHeader().getMsgId(),this.getPeerController().getLocalPeer().getId(), queryResponseMsg.getAllResults());
				}
			} else {
				/*Como ya hay un task que espera los resultados de la propagación y mergea la info de este 
				con la de los vecino, se envía un mensaje con resultado vacío la para evitar duplicados
				en los resultados*/
				Logger.logDebug(this.getClass().getName(), "Query "+msg.getHeader().getMsgId()+" en proceso");
				peer.sendEmptyQueryResponse(msg.getHeader().getMsgId());
			}
		}
			
	}

	/**
	 * Se dispara este metodo cuando se recibe un resultado de una query por parte de un peer.
	 * Simplemente se acumula el resultado retornado desde este con el resto para luego propagar la
	 * respuesta hasta que vuelva a el origen
	 * 
	 * @param peer
	 * @param msg
	 */
	public void receiveQueryResponse(Peer peer, QueryResponseMessage msg) {
		Logger.logInfo(this.getClass().getName(), "Recibo query de "+peer.toString());
		this.getTaskController().addResultsToTask(msg.getHeader().getMsgId(), peer.getId(), msg.getAllResults());
	}
	
	/**
	 * Finalizado el task, por resultados completos o por expiracion de tiempo, se procede a enviar
	 * la respuesta al peer originario del ping.
	 * 
	 * @param msgId
	 */
	public void sendQueryResponse(String msgId) {
		Logger.logInfo(this.getClass().getName(), "Termina task y envio el resultado del query");
		Task task = this.getTaskController().removeTask(msgId);
		if (task != null) {
			Iterator<Result>  resultsIt = task.getAllResults().iterator(); 
			QueryResponseMessage msg = (QueryResponseMessage) MessageFactory.getMessage(MessageFactory.CODE_QUERY_RESP);
			msg.getHeader().setMsgId(task.getMsgId());
			while(resultsIt.hasNext()) {
				QueryResultVO vo = (QueryResultVO) resultsIt.next();
				msg.addResult(new QueryResult(vo));
			}
			Logger.logInfo(this.getClass().getName(), "Envio query response a "+task.getSourcePeerId());
			this.getPeerController().getPeer(task.getSourcePeerId()).sendQueryResponse(msg);
		}
	}
	
	public void cleanIndex() {
		this.cleanCaches();
		this.getIndexController().deleteTf(null);
		this.getIndexController().deleteDoc(null);
		this.getIndexController().deleteWord(null);
		this.getIndexController().deleteWordFreq(null);
		this.getIndexController().initSequences();
	}
	
	public void cleanCaches() {
		this.getIndexController().cleanCaches();
	}
	
	public long[] reIndexFiles(Integer nFiles) {
		this.indexUpdateLaunched = true;
		this.cleanIndex();
		File dir = new File("/mnt/sdcard/"+Params.CORPUS_DIR);
		File[] fileArr = dir.listFiles();
		File file;
		Long bytes = new Long(0);
		Long pFiles = new Long(0);
		long begin = System.currentTimeMillis();
		if (fileArr != null) {
			if ((nFiles==null) || (nFiles > fileArr.length)) {
				nFiles = fileArr.length;
			}
			for (int i=0;i<=nFiles-1;i++) {
				file = fileArr[i];
				Logger.logDebug(AppModule.class.getSimpleName(), " Indexo "+file.getName());
				this.addFile(file, file.getName());
				bytes += file.length();
				pFiles++;
			}
		}
		this.indexUpdateLaunched = false;
		return new long[]{pFiles.longValue(),bytes.longValue(),System.currentTimeMillis() - begin};
	}
	
	public String queryTest(Integer nFiles) {
		String nFilesStr = String.valueOf(nFiles);
		int nWords = this.getIndexController().findAllWords().size();
		Random random = new Random();
		int id;
		WordVO word;
		long beginMillis;
		int nResult ;
		StringBuffer resultBuffer = new StringBuffer();
		resultBuffer.append("|OK|\n");
		int id1;
		StringBuffer queryBuffer;
		int id2;
		// PROCESAMIENTO DE QUERY PERFORMANTE
		Params.PROCESS_QUERY_PERFORMED = true;
		for (int i = 1; i<= 30; i++) {
			id	= random.nextInt(nWords)+1;
			word = this.getIndexController().findWordById(id);
			beginMillis = System.currentTimeMillis();
			nResult = this.processQuery(word.getName()).size();
			resultBuffer.append(nFilesStr+"|1|"+String.valueOf(System.currentTimeMillis() - beginMillis)+"|"+id+"|"+nResult+"\n");
		}
		for (int i = 1; i<= 30; i++) {
			queryBuffer = new StringBuffer();
			id	= random.nextInt(nWords)+1;
			word = this.getIndexController().findWordById(id);
			queryBuffer.append(word.getName()+" ");
			id1	= random.nextInt(nWords)+1;
			while (id == id1) {
				id1	= random.nextInt(nWords)+1;
			}
			word = this.getIndexController().findWordById(id1);
			queryBuffer.append(word.getName());
			beginMillis = System.currentTimeMillis();
			nResult = this.processQuery(queryBuffer.toString()).size();
			resultBuffer.append(nFilesStr+"|2|"+String.valueOf(System.currentTimeMillis() - beginMillis)+"| & "+id+"-"+id1+"|"+nResult+"\n");
		}
		for (int i = 1; i<= 30; i++) {
			queryBuffer = new StringBuffer();
			id	= random.nextInt(nWords)+1;
			word = this.getIndexController().findWordById(id);
			queryBuffer.append(word.getName()+" ");
			id1	= random.nextInt(nWords)+1;
			while (id == id1) {
				id1	= random.nextInt(nWords)+1;
			}
			word = this.getIndexController().findWordById(id1);
			queryBuffer.append(word.getName()+" ");
			id2	= random.nextInt(nWords)+1;
			while (id2 == id1 || id2 == id ) {
				id2 = random.nextInt(nWords)+1;
			}
			word = this.getIndexController().findWordById(id2);
			queryBuffer.append(word.getName());
			beginMillis = System.currentTimeMillis();
			nResult = this.processQuery(queryBuffer.toString()).size();
			resultBuffer.append(nFilesStr+"|3|"+String.valueOf(System.currentTimeMillis() - beginMillis)+"| & "+id+"-"+id1+"-"+id2+"|"+nResult+"\n");
		}
		// PROCESAMIENTO DE QUERY BOOLEANO
		Params.BOOLEAN_EXTENDED_MODE = true;
		for (int i = 1; i<= 30; i++) {
			queryBuffer = new StringBuffer();
			id	= random.nextInt(nWords)+1;
			word = this.getIndexController().findWordById(id);
			queryBuffer.append(word.getName()+" OR ");
			id1	= random.nextInt(nWords)+1;
			while (id == id1) {
				id1	= random.nextInt(nWords)+1;
			}
			word = this.getIndexController().findWordById(id1);
			queryBuffer.append(word.getName());
			beginMillis = System.currentTimeMillis();
			nResult = this.processQuery(queryBuffer.toString()).size();
			resultBuffer.append(nFilesStr+"|2|"+String.valueOf(System.currentTimeMillis() - beginMillis)+"| OR "+id+"-"+id1+"|"+nResult+"\n");
		}
		for (int i = 1; i<= 30; i++) {
			queryBuffer = new StringBuffer();
			id	= random.nextInt(nWords)+1;
			word = this.getIndexController().findWordById(id);
			queryBuffer.append(word.getName()+" OR ");
			id1	= random.nextInt(nWords)+1;
			while (id == id1) {
				id1	= random.nextInt(nWords)+1;
			}
			word = this.getIndexController().findWordById(id1);
			queryBuffer.append(word.getName()+" OR ");
			id2	= random.nextInt(nWords)+1;
			while (id2 == id1 || id2 == id ) {
				id2 = random.nextInt(nWords)+1;
			}
			word = this.getIndexController().findWordById(id2);
			queryBuffer.append(word.getName());
			beginMillis = System.currentTimeMillis();
			nResult = this.processQuery(queryBuffer.toString()).size();
			resultBuffer.append(nFilesStr+"|3|"+String.valueOf(System.currentTimeMillis() - beginMillis)+"| OR "+id+"-"+id1+"-"+id2+"|"+nResult+"\n");
		}
		Params.BOOLEAN_EXTENDED_MODE = false;
		return resultBuffer.toString();
	}
	
}
